using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.SharePoint;
using Microsoft.SharePoint.WebControls;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Text.RegularExpressions;

namespace TLingenf.SharePoint.CustomFieldTypes
{
    /// <summary>
    /// Custom Field type for storing passwords.  Value is only displayed to  users with edit permissions.
    /// </summary>
    public class PasswordField : SPField
    {
        #region Globals: Constants and Variables

        // custom property mappings
        private const string PROP_MINCHAR_IDX = "MinChars";
        private const string PROP_STRONG_IDX = "StrongOnly";

        // values that determine logic for the field type
        private const int VAL_MAX_CHAR = 15;
        private const string VAL_MASK = "******";
        private const string VAL_ERR_MSG_STRONG = "Password must have 3 of the 4 following: (1) at least 1 upper case character (2) at least 1 lower case character (3) at least 1 numerical character (4) at least 1 special character. Minimum Characters: {0}";
        private const string VAL_ERR_MSG_WEAK = "Password must have at least {0} characters.";

        #endregion

        #region PasswordField: Constructors

        public PasswordField(SPFieldCollection fields, string fieldName)
            : base(fields, fieldName)
        {
            // do nothing
        }

        public PasswordField(SPFieldCollection fields, string typeName, string displayName)
            : base(fields, typeName, displayName)
        {
            // do nothing
        }

        #endregion

        #region Field Column: Common Method Overrides

        public override BaseFieldControl FieldRenderingControl
        {
            get
            {
                BaseFieldControl fieldControl = new PasswordFieldControl();
                fieldControl.FieldName = InternalName;
                return fieldControl;
            }
        }

        #endregion

        #region Field Column: Value Method Overrides

        /// <summary>
        /// The value of the field.  If the user does not have the proper permissions a masked value is returned.
        /// </summary>
        /// <param name="value"></param>
        /// <returns>The password or masked value to present to the user.</returns>
        public override object GetFieldValue(string value)
        {
            /* test to return the action value or a masked value depending on the permissions allowed
             * for the current user. */
            if (this.UserHasEditPermission(SPContext.Current.Web.CurrentUser))
            {
                // The user is allowed to see actual value.
                return base.GetFieldValue(value);
            }
            else
            {
                // The user has not been authorized to see the actual value, return a masked value.
                return VAL_MASK;
            }
        }

        /// <summary>
        /// Determines if the value is a valid password.  Throws an SPFieldValidationException if the
        /// value is not valid.
        /// </summary>
        /// <param name="value">The value to test.</param>
        /// <returns>A valid string value for the password.</returns>
        public override string GetValidatedString(object value)
        {
            string textValue = value.ToString();

            // allow empty value if field is not required, stop processing.
            if (textValue.Length.Equals(0) && !this.Required)
                return string.Empty;

            // compare size of value to determine if the string length is valid.
            if ((textValue.Length >= this.MinChars) || (textValue.Length <= VAL_MAX_CHAR))
            {
                //Setup regular expression based on custom property
                Regex reg = new Regex(this.BuildValidationExpression());

                if (reg.IsMatch(textValue))
                {
                    // the entered password has met requirements
                    return textValue;
                }
            }
            
            /* If a value has been entered and it has not meet the requirements, throw an error of type 
             * SPFieldValidatorException and present the user with a custom exception message. */
            throw new SPFieldValidationException(this.GetValidationErrorString());
        }

        #endregion

        #region PasswordField: Events and Helper Methods

        /// <summary>
        /// Get the validation logic used when determining if the password value meets the complexity requirements.
        /// </summary>
        /// <returns>The regular expression used to validate the password.</returns>
        private string BuildValidationExpression()
        {
            /* this method will return a different expression based upon the value of the  property 
             * defining the password strength setting.  */
            if (this.StrongOnly)
            {
                // return the strong password validation regular expression
                return string.Concat("(?=^.{", this.MinChars, ",", VAL_MAX_CHAR, "}$)((?=.*\\d)(?=.*[A-Z])(?=.*[a-z])|(?=.*\\d)(?=.*[^A-Za-z0-9])(?=.*[a-z])|(?=.*[^A-Za-z0-9])(?=.*[A-Z])(?=.*[a-z])|(?=.*\\d)(?=.*[A-Z])(?=.*[^A-Za-z0-9]))^.*");
            }
            else
            {
                // return the simple password validation regular expressions
                return string.Concat("^\\w{", this.MinChars, ",", VAL_MAX_CHAR, "}$");
            }
        }

        /// <summary>
        /// Determine if a user can view/edit passwords.
        /// </summary>
        /// <param name="user">The user whose permissions will be tested.</param>
        /// <returns>A true or false value indicating if the user has the appropriate permissions.</returns>
        public bool UserHasEditPermission(SPUser user)
        {
            /* test if a new item is beging created or an existing item is being edited/viewed.
             * New items have not yet been assisned an ID and will have a value of 0. */
            if (SPContext.Current.ListItem.ID.Equals(0))
            {
                /* a new item is being created.  Determine if the user has add or edit permissions on the list. */
                return SPContext.Current.List.DoesUserHavePermissions(SPBasePermissions.AddListItems | SPBasePermissions.EditListItems);
            }
            else
            {
                /* viewing/editing an existing item.  Check the permission on the current list item.
                 * This is important in case item level security has been used. */
                return SPContext.Current.ListItem.DoesUserHavePermissions(SPBasePermissions.EditListItems);
            }
        }

        #endregion

        #region PasswordField: Custom Properties

        /// <summary>
        /// Get or set the minimum length allowed for a password if one is entered.
        /// </summary>
        public int MinChars
        {
            get { return Convert.ToInt32(this.GetCustomProperty(PROP_MINCHAR_IDX)); }
            set { this.SetCustomProperty(PROP_MINCHAR_IDX, value); }
        }

        /// <summary>
        /// Establish the password strength required for passwords.
        /// </summary>
        public bool StrongOnly
        {
            get { return Convert.ToBoolean(this.GetCustomProperty(PROP_STRONG_IDX)); }
            set { this.SetCustomProperty(PROP_STRONG_IDX, value); }
        }

        /// <summary>
        /// The message that is displayed when a validation error occurs.
        /// </summary>
        /// <returns></returns>
        public string GetValidationErrorString()
        {
            // show a different message based on password strength
            string msg = this.StrongOnly ? VAL_ERR_MSG_STRONG : VAL_ERR_MSG_WEAK;
            return string.Format(msg, this.MinChars);
        }

        #endregion
    }
}
